# Edge Functions

> Supacharger Edge Functions are TypeScript services deployed globally through Supabase. Keep their source under `supabase/functions/`, review it with the application, and deploy it separately from the Next.js/Vercel release.

# Edge Functions

Supacharger Edge Functions are TypeScript services deployed globally through Supabase. Keep their source under `supabase/functions/`, review it with the application, and deploy it separately from the Next.js/Vercel release.

Treat every Edge Function as a server trust boundary. Validate structured request data before privileged work or RPC calls and retain separate authentication, authorisation, file-content, and database protections. See [Input validation](./input-validation.md) for the shared Zod and PostgreSQL contract.

Edge Functions use Deno rather than the Next.js Node.js runtime. The application's root `tsconfig.json` therefore excludes `supabase/functions/**/*`; this prevents `npm run build` from trying to resolve Deno globals, explicit `.ts` imports, URL imports, and `jsr:` packages. Keep that exclusion when upgrading or creating a consumer. Validate Edge Function source with the dedicated Deno tests described below.

## Secure image resizing and compression

`process-image-upload` is the reusable authenticated image processor installed by Supacharger. It accepts JPEG, PNG, or WebP source images, applies a code-owned resize/encoding policy, uploads the result to Supabase Storage, and returns the stored path plus dimensions and the configured URL form.

The function is not public. `supabase/config.toml` keeps `verify_jwt = true`, and the handler authenticates with `withSupabase({ auth: 'user' })`. The upload uses the caller's RLS-scoped Supabase client. It does not use a secret/service-role key.

### Request

Send multipart form data with only:

- `file`: the source image; and
- `target`: a configured target name such as `profile_avatar` or `profile_header`.

```ts
const body = new FormData();
body.set("file", imageFile);
body.set("target", "profile_avatar");

const { data, error } = await supabase.functions.invoke(
  "process-image-upload",
  {
    body,
  },
);
```

Never send a bucket, destination folder, filename, output format, width, height, quality, compression value, visibility, or user ID. Those values are intentionally absent from the HTTP contract.

### Target policy

Applications own `supabase/functions/_shared/image-targets.ts`. Each target fixes:

- Storage bucket and path below the authenticated user's ID;
- server-generated filename prefix;
- JPEG or PNG output;
- `inside` or centred `cover` resizing;
- maximum width and height;
- JPEG quality percentage or lossless PNG compression effort;
- input byte and decoded-pixel limits;
- cache control; and
- private signed-URL or intentional public-URL behaviour.

The default targets are:

| Target           | Destination                                        | Result                                                         |
| ---------------- | -------------------------------------------------- | -------------------------------------------------------------- |
| `profile_avatar` | `user-avatars/<user-id>/avatar-<uuid>.jpg`         | centred 300×300 maximum JPEG, quality 80, one-hour signed URL  |
| `profile_header` | `user-avatars/<user-id>/headers/header-<uuid>.jpg` | centred 1600×600 maximum JPEG, quality 80, one-hour signed URL |

The private `user-avatars` bucket remains protected by Storage RLS. The function never upscales, strips embedded metadata after applying image orientation, composites JPEG transparency onto white, generates a UUID filename, and uploads with `upsert: false`.

To add lossless PNG output, create an application target with `outputFormat: 'png'` and `pngCompressionPercent`. PNG compression changes encoding effort/size without discarding pixels. JPEG targets use `qualityPercent` from 1–100.

### Helper functions

The resize implementation is split into reusable helper modules so product-owned Edge Functions can add image flows without copying the HTTP handler. `defineImageUploadTargets()` validates the checked-in target map at load time, including safe names, path prefixes, dimensions, byte limits, pixel limits, quality, compression, visibility and signed-URL rules.

`transformAndStoreImage()` is the shared server-side helper that receives a `File`, authenticated Supabase client, selected target and current user ID. It rejects unsupported MIME types, enforces the target byte limit, decodes with the pinned ImageMagick WASM runtime, applies the target resize mode, strips metadata, writes JPEG or PNG output, stores the object below the user's Storage prefix, and returns the stored path, dimensions, byte size, content type and configured public or signed URL.

`supportsImageMimeType()` is available for early request checks before invoking the heavier transformer. The resize internals support `inside` for proportional downscaling within maximum dimensions and centred `cover` for fixed-ratio crops such as avatars and headers. Both modes deliberately avoid upscaling.

Assume every signed-in user can call every configured target directly. A target is safe only when its Storage policy authorises that caller and its path is scoped to the caller's user ID. If a destination belongs to a project, organisation, or another resource, create a narrow wrapper that verifies current membership/ownership before using the shared transformer; do not accept an unchecked resource ID in the generic function.

## Limits and errors

Starter targets accept source files up to 5 MiB and 25 megapixels after decoding. Target policy may lower these limits and is capped at 10 MiB, 40 megapixels, and 4096 pixels per configured output dimension. Hosted Edge Function CPU and memory limits still apply; keep transforms small and move batch or heavyweight media work to a background service.

Common responses are:

- `401` for a missing or invalid user session;
- `400` for a missing file or unknown target;
- `413` when the source exceeds the target byte limit;
- `415` for an unsupported declared or decoded input type;
- `422` for corrupt images or excessive decoded dimensions; and
- `500` when Storage or signed-URL creation fails.

## Local verification and deployment

```bash
npm run test:image-transform
npm run test:image-transform-runtime
npx supabase functions serve process-image-upload
npx supabase functions deploy process-image-upload
```

The automated runtime test loads the pinned ImageMagick WASM package and verifies real JPEG and PNG output. Before deploying a new target, also test its Storage RLS with a real authenticated user. Do not use `--no-verify-jwt` for deployment.

The Edge Function pins `@imagemagick/magick-wasm`, `@supabase/server`, and the Supabase client version used for types. Native Node image libraries such as Sharp are not supported in the Supabase Edge runtime; use the checked-in WASM transformer.
